package com.dd.framework.aspectj;

import cn.hutool.json.JSONUtil;
import com.dd.common.entity.WebLog;
import com.dd.framework.aspectj.annotation.Log;
import com.dd.framework.util.ServletUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.marker.Markers;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * 通用日志切面
 * 需求：
 *    用户请求后记录用户访问参数，用户身份，所做操作，
 *    用户请求处理完成后记录处理结果
 * 实现方案
 *      改类将对controller类中的方法进行切面处理
 *      记录日志使用两种方案
 *      1. 当方法上配置 @log 注解时，
 *          将会根据方法上配置的@log注解构建日志对象weblog
 *      2. 当方法上未配置 @log 注解时
 *          将会根据方法上配置的@Api  @ApiOperation 注解构建日志对象weblog
 */
@Slf4j
@Aspect
@Component
public class LogAspectj {

    @Pointcut("execution(public * com.dd.web.base.controller.*Controller.*(..))")
    public void webLog(){


    }


    @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ServletUtils.getRequest();
        // 记录下请求内容
//        System.out.println("URL : " + request.getRequestURL().toString());
//        System.out.println("HTTP_METHOD : " + request.getMethod());
//        System.out.println("IP : " + request.getRemoteAddr());
//        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
//        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //首先解读注解中的模块 功能名称
        WebLog webLog = this.readAnnotationFromMethod(method);
        webLog.setIp(request.getRemoteAddr());
        webLog.setStartTime(new Date());
        webLog.setUrl(request.getRequestURL().toString());
        webLog.setMethod(request.getMethod());
        webLog.setParameter(getParameter(method, joinPoint.getArgs()));
        log.info(Markers.appendFields(webLog), JSONUtil.parse(webLog).toString());
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {

    }

    //后置异常通知
    @AfterThrowing(value="webLog()", throwing = "e")
    public void throwss(JoinPoint jp, Exception e){

    }

    //后置最终通知,final增强，不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){

    }

    /**
     * 解析方法上的注解 获取模块 功能名称
     * 优先解读@Log
     * 如果没有@Log 则解读 @API @ApiOperation
     * @param method
     * @return
     */
    private WebLog readAnnotationFromMethod(Method method){
        WebLog webLog = null;
        Log logAnnotation = method.getAnnotation(Log.class);
        if (logAnnotation != null){
            //方法上有 @Log 注解则解读Log注解
            webLog = this.readAnnotationLog(logAnnotation,method);
        }else{
            //方法上没有 @Log 注解则解读ApiOperation注解
            webLog = this.readAnnotationApi(method);
        }
        return webLog;
    }

    /**
     * 解读方法上的 @Log 注解内容构建WebLog日志内容对象
     * 1. 优先根据方法上的@Log 注解解读 如果方法上的@Log无module属性，将从类的@Log 注解解读
     * @param annotationLog 方法上的@Log 对象
     * @param targetMethod 目标方法
     * @return 解读到的日志对象
     */
    private WebLog readAnnotationLog(Log annotationLog,Method targetMethod){
        WebLog wl = new WebLog();
        //判断方法上的 @Log注解是否有模块属性
        if (StringUtils.isNotBlank(annotationLog.module())){
            wl.setModule(annotationLog.module());
        }else{
            //方法上的 @Log注解没有模块属性，则从类上的 @Log注解获取模块属性
            Class c = targetMethod.getDeclaringClass();
            Log annotationLogFromC = (Log) c.getAnnotation(Log.class);
            if (annotationLogFromC != null){
                wl.setModule(annotationLogFromC.module());
            }
        }

        //从注解中获取 功能名称
        wl.setFunction(annotationLog.function());
        return wl;
    }

    /**
     * 解读方法上的 @ApiOperation 注解内容构建WebLog日志内容对象
     * @param targetMethod 目标方法
     * @return 解读到的日志对象
     */
    private WebLog readAnnotationApi( Method targetMethod){
        ApiOperation annotationApi = targetMethod.getAnnotation(ApiOperation.class);
        WebLog wl = new WebLog();
        //判断方法上的 @Log注解是否有模块属性
        if (StringUtils.isNotBlank(annotationApi.value())){
            wl.setFunction(annotationApi.value());
        }
        //方法上的 注解中获取 功能名称
        Class c = targetMethod.getDeclaringClass();
        Api annotationLogFromC = (Api) c.getAnnotation(Api.class);
        if (annotationLogFromC != null){
            wl.setModule(annotationLogFromC.value());
        }

        return wl;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!org.springframework.util.StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }




}
